

#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>

#include "soc.h"
#include "dbg_cli.h"



typedef struct _tag_clk_info {
	int enable_ofs;		/* REG + BIT */
	int bypass_ofs;		/* REG + BIT */
	int divide_ofs;		/* REG */
} clock_info_t;



#define CLK_CPU_AXI0		0  				// only for A35 ???

#define CLK_FAB_AXI4		1				// FABRIC_AXI4
#define CLK_FAB_AXI6		2				// FABRIC_AXI6
#define CLK_FAB_AXI_VCD     3               // clk_axi_video_codec
#define CLK_FAB_AXI_VIP     4               // clk_axi_vip

#define CLK_ROM_AHB			5
#define CLK_DDR_REG			6

#define CLK_GPIO_APB		7               // GPIO, pclk 
#define CLK_GPIO_INT		8				// GPIO, intr sync
#define CLK_GPIO_DB			9               // GPIO, dbclk

#define CLK_SPI_APB0		10              // SPI0, pclk
#define CLK_SPI_APB1		11              // SPI0, pclk
#define CLK_SPI_APB2		12              // SPI0, pclk
#define CLK_SPI_APB3		13              // SPI0, pclk
#define CLK_SPI_SSI			14              // SPIx, ssi_clk

#define CLK_TPU_FAB			15              // TPU, fabric ??
#define CLK_TPU_SYS			16              // TPU, intrenal

// video subsys, video codec
#define CLK_VCD_SRC0		16              // VCD, 
#define CLK_VCD_SRC1		16              // VCD, 
#define CLK_VCD_SRC2		16              // VCD, 

#define CLK_VCD_H264_AXI	16              // VCD, 
#define CLK_VCD_H265_AXI	16              // VCD, 
#define CLK_VCD_JPEG_AXI	16              // VCD, 

#define CLK_VCD_H264_APB	16              // VCD, 
#define CLK_VCD_H265_APB	16              // VCD, 
#define CLK_VCD_JPEG_APB	16              // VCD, 


#define REG_OFS(reg, bit) 		((reg << 8) + bit)


static clock_info_t  clk_info_array[] = {

	{ REG_OFS(0,  1),  	REG_OFS(0,  1),   0x48  },		//    CLK_AXI_CPU0
	{ REG_OFS(2,  1),  	REG_OFS(0, 19),   0xB8  },		//    CLK_FAB_AXI4 : fpll, disppll;
	{ REG_OFS(2,  2),  	REG_OFS(0, 20),   0xBC  },		//    CLK_FAB_AXI6 : fpll;
	{ REG_OFS(2,  8),  	REG_OFS(0, 26),   0xE4  },		// CLK_FAB_AXI_VCD : a0pll, mipipll, cam1pll, fpll
	{ REG_OFS(2,  4),  	REG_OFS(0, 22),   0xC8  },		// CLK_FAB_AXI_VIP : mipipll, cam0pll, disppll, fpll

	{ REG_OFS(0,  6),		        -1,     -1, },		// CLK_ROM_AHB  : AXI4;
	{ REG_OFS(0,  7),		        -1,     -1, },		// CLK_DDR_REG  : AXI6;

	{ REG_OFS(0, 29),		        -1,     -1, },		// CLK_GPIO_APB : AXI6;
	{ REG_OFS(0, 30),		        -1,     -1, },		// CLK_GPIO_INT : AXI6;
	{ REG_OFS(0, 31),		        -1,   0x94, },		// CLK_GPIO_APB : CLK_1M;

	{ REG_OFS(1,  9),		        -1,     -1, },		// CLK_SPI_APB0 : AXI4;
	{ REG_OFS(1, 10),		        -1,     -1, },		// CLK_SPI_APB0 : AXI4;
	{ REG_OFS(1, 11),		        -1,     -1, },		// CLK_SPI_APB0 : AXI4;
	{ REG_OFS(1, 12),		        -1,     -1, },		// CLK_SPI_APB0 : AXI4;
	{ REG_OFS(3,  6),	REG_OFS(0, 30),  0x100, },		//  CLK_SPI_SSI : fpll;

	{ REG_OFS(0,  4),		        -1,     -1, },		//  CLK_TPU_FAB : mipipll
	{ REG_OFS(0,  5),	REG_OFS(0,  3),   0x54, },		//  CLK_TPU_SYS : tpll, a0pll, mipipll, fpll
	

	{ REG_OFS(2,  9),	REG_OFS(0, 27),   0xEC, },		//  CLK_VCD_SRC0 : disppll, mipipll, cam1pll, fpll
	{ REG_OFS(3, 28),	REG_OFS(1,  0),     -1, },		//  CLK_VCD_SRC1 : cam1pll
	{ REG_OFS(4,  3),	REG_OFS(1,  3),     -1, },		//  CLK_VCD_SRC2 : fpll

	{ REG_OFS(2, 10),		        -1,     -1, },		// CLK_VCD_H264_AXI : CLK_FAB_AXI_VCD;
	{ REG_OFS(2, 11),		        -1,     -1, },		// CLK_VCD_H265_AXI : CLK_FAB_AXI_VCD;
	{ REG_OFS(2, 12),		        -1,     -1, },		// CLK_VCD_JPEG_AXI : CLK_FAB_AXI_VCD;
	
	{ REG_OFS(2, 14),		        -1,     -1, },		// CLK_VCD_H264_APB : AXI6;
	{ REG_OFS(2, 15),		        -1,     -1, },		// CLK_VCD_H265_APB : AXI6;
	{ REG_OFS(2, 13),		        -1,     -1, },		// CLK_VCD_JPEG_APB : AXI6;


};




void cvclk_enable( int cidx )
{
	int offs;
	intptr_t addr;
	uint32_t temp;

	/**/
	if ( cidx < 0 || (cidx > sizeof(clk_info_array) / sizeof(clock_info_t) ) )  {
		return;
	}

	//
	offs = clk_info_array[cidx].enable_ofs;
	if ( offs < 0 )  {
		return;
	}

	// 
	addr = CLK_DIV_BASE + (offs >> 6);
	temp = read_reg( addr );
	temp = temp | (1ul << (offs & 0x1F));
	write_reg( addr, temp );
	return;
}


void cvclk_disable( int cidx )
{
	int offs;
	intptr_t addr;
	uint32_t temp;

	/**/
	if ( cidx < 0 || (cidx > sizeof(clk_info_array) / sizeof(clock_info_t) ) )  {
		return;
	}

	//
	offs = clk_info_array[cidx].enable_ofs;
	if ( offs < 0 )  {
		return;
	}

	// 
	addr = CLK_DIV_BASE + (offs >> 6);
	temp = read_reg( addr );
	temp = temp & ~(1ul << (offs & 0x1F));
	write_reg( addr, temp );
	return;
}


void cvclk_bypass_enb( int cidx )
{
	int offs;
	intptr_t addr;
	uint32_t temp;

	/**/
	if ( cidx < 0 || (cidx > sizeof(clk_info_array) / sizeof(clock_info_t) ) )  {
		return;
	}

	//
	offs = clk_info_array[cidx].bypass_ofs;
	if ( offs < 0 )  {
		return;
	}

	// 
	addr = CLK_DIV_BASE + 0x30 + (offs >> 6);
	temp = read_reg( addr );
	temp = temp | (1ul << (offs & 0x1F));
	write_reg( addr, temp );
	return;
}


void cvclk_bypass_dis( int cidx )
{
	int offs;
	intptr_t addr;
	uint32_t temp;

	/**/
	if ( cidx < 0 || (cidx > sizeof(clk_info_array) / sizeof(clock_info_t) ) )  {
		return;
	}

	//
	offs = clk_info_array[cidx].bypass_ofs;
	if ( offs < 0 )  {
		return;
	}

	// 
	addr = CLK_DIV_BASE + 0x30 + (offs >> 6);
	temp = read_reg( addr );
	temp = temp & ~(1ul << (offs & 0x1F));
	write_reg( addr, temp );
	return;
}



/*
选择 clock source, 大多数只有一个, 少量有两个.
设置 divider

    [0] : 0 = Reset.
    [3] : 0 = default initial divider,  1 = divider from [20:16]
  [9:8] : clock source
[20:16] : clock divider factor

*/
void cvclk_divider( int cidx, int src, int div )
{
	int offs;
	intptr_t addr;
	uint32_t temp;

	/**/
	if ( cidx < 0 || (cidx > sizeof(clk_info_array) / sizeof(clock_info_t) ) )  {
		return;
	}

	//
	offs = clk_info_array[cidx].divide_ofs;
	if ( offs < 0 )  {
		return;
	}

	//
	addr = CLK_DIV_BASE + offs;
	write_reg( addr, 0 );

	//
	temp = div;
	temp = (temp << 8) | (src & 0x3);
	temp = (temp << 8) | 0x9;
	write_reg( addr, temp );
	return;
}



int bl2_dbg_clock( void * pctx, int argc, const char * argv[] )
{
	int iret;
	uint32_t temp;
	int idx;
	int src;
	int div;

	if ( argc < 3 )  {
		goto usage;
	}

	if ( 0 == strcmp( argv[1], "byp" ) )  {

		iret = debug_str2uint( argv[2], &temp );
		if ( iret != 0 )  {
			printf( "idx fmt err = %d\n", iret );
			goto usage;
		}

		/**/
		cvclk_bypass_enb( (int)temp );
		return 0;

	} else if ( 0 == strcmp( argv[1], "div" ) )  {

		iret = debug_str2uint( argv[2], &temp );
		if ( iret != 0 )  {
			printf( "idx fmt err = %d\n", iret );
			goto usage;
		}

		idx = (int)temp;

		iret = debug_str2uint( argv[3], &temp );
		if ( iret != 0 )  {
			printf( "src fmt err = %d\n", iret );
			goto usage;
		}

		src = (int)temp;

		iret = debug_str2uint( argv[4], &temp );
		if ( iret != 0 )  {
			printf( "div fmt err = %d\n", iret );
			goto usage;
		}

		div = (int)temp;

		/* bypass */
		cvclk_bypass_enb( idx );
		cvclk_divider( idx, src, div );
		cvclk_bypass_dis( idx );
		return 0;
	}

usage:
	printf( "%s byp <idx> \n", argv[0] );
	printf( "%s div <idx>  <src> <div> \n", argv[0] );
	return 0;
}



